// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
using System;
using System.Threading;
using log4net;

namespace KnessetCore.Engines
{
	/// <summary>
	/// A base class used by all engine types. An engine runs a set of operation in a timely manner.
	/// </summary>
	abstract public class Engine
	{
		protected Timer executionTimer;
		/// <summary>
		/// At what hour of the day to run the engine operation.
		/// </summary>
		private byte hour;
		/// <summary>
		/// At what minute of the hour to run the engine operation.
		/// </summary>
		private byte minute;
		/// <summary>
		/// Log data.
		/// </summary>
		private static readonly ILog logger = LogManager.GetLogger("KnessetCore.Engines");

		/// <summary>
		/// Run a single engine operation in a timely manner
		/// </summary>
		/// <param name="dueTime">The amount of time to delay before a single operation is invoked, in milliseconds. 
		/// Specify Timeout.Infinite to prevent the timer from starting. 
		/// Specify zero (0) to start the timer immediately. </param>
		/// <param name="period">The time interval between invocations of a single operation, in milliseconds. 
		/// Specify Timeout.Infinite to disable periodic signaling.</param>
		public void Start(long dueTime, long period)
		{
			// Check values are not negative.
			if (dueTime < 0 || period <= 0)
			{
				throw new ArgumentOutOfRangeException("dueTime or period are negative");
			}
			logger.Info(string.Format("Running Engine every {0} milliseconds. Due time is: {1}", period, dueTime));
			executionTimer = new Timer(timerCallback, null, dueTime, period);
		}

		/// <summary>
		/// Run a single engine operation in in a timely manner. The operation is run in the same hour/minute 
		/// every day. The engine assures that the operation will occur exactly only once.
		/// </summary>
		/// <param name="hour">At what hour of the day to run the engine operation. 
		/// Expressed as a value between 0 and 23 </param>
		/// <param name="minute">At what minute of the hour to run the engine operation.
		/// expressed as a value between 0 and 59.</param>
		public void Start(byte hour, byte minute)
		{
			// Check values are not negative.
			if (hour < 0 || minute <= 0 || hour > 23 || minute > 59)
			{
				throw new ArgumentOutOfRangeException("Hour or minute values are not in range.");
			}
			this.hour = hour;
			this.minute = minute;
			logger.Info(string.Format("Running Engine only on hour {0} and minute {1}", hour, minute));
			// Run the timer every minute.
			executionTimer = new Timer(hourMinuteTimerCallback, null, 0, 60000);
		}

		/// <summary>
		/// Callback used by the timer.
		/// </summary>
		protected void timerCallback(Object stateInfo)
		{
			Start();
		}

		/// <summary>
		/// Callback used by the timer.
		/// </summary>
		 protected void hourMinuteTimerCallback(Object stateInfo)
		{
			DateTime now = DateTime.Now;
			// If hour and minute are correct then execute.
			if (now.Minute == minute && now.Hour == hour)
			{
				Start();
			}
		}
		/// <summary>
		/// Execute a list of actions.
		/// </summary>
		public abstract void Start();
	}
}